package com.googlecode.sparda.commons.performance;

import com.googlecode.sparda.commons.constant.TimeConstant;


/**
 * Class that represent a {@link CachedValue} whit a constant validity time.
 * The typical use of a {@link ThreadTimedCachedValue} occurs when an onerous computation must be done (we can't),
 * but we want the value cached to be invalidated after a precisely defined time intervals.
 * This class uses an invalidator {@link Thread} to invalidate the cache.
 * @author Fabio Grucci (<i>alias</i> <b>Phoenix</b>)
 * @version 1.0
 * @since 1.0
 * 
 */
public abstract class ThreadTimedCachedValue<T> extends CachedValue<T> {

	/**
	 * The {@link ThreadGroup} of the invalidator.
	 */
	private static final ThreadGroup INVALIDATOR_THREADGROUP = new ThreadGroup("TimedChachedValue invalidator");
	
	/**
	 * Runnable that invalidate the {@link CachedValue}.
	 */
	private final InvalidateValue invalidateValueRunnable;
	
	/**
	 * The latest (or current) {@link Thread} executed (in execution).
	 */
	private Thread lastThread;
	
	/**
	 * Build a {@link ThreadTimedCachedValue} with a 
	 * validity time of one minute.
	 */
	public ThreadTimedCachedValue() {
		this(TimeConstant.ONE_MINUTE);
	}
	
	/**
	 * Build a {@link ThreadTimedCachedValue} with a period of validity. 
	 * @param period The period of validity of the value in milliseconds.
	 */
	public ThreadTimedCachedValue(long period) {
		this.invalidateValueRunnable = new InvalidateValue(period);
		this.lastThread = new Thread(INVALIDATOR_THREADGROUP, this.invalidateValueRunnable);
		this.lastThread.start();
	}
	
	/**
	 * Build a {@link ThreadTimedCachedValue} with a period of validity 
	 * of one minute and an initial value.
	 * @param initialValue The starting value for the variable
	 */
	public ThreadTimedCachedValue(T initialValue) {
		this(initialValue, TimeConstant.ONE_MINUTE);
	}
	
	/**
	 * Build a {@link ThreadTimedCachedValue} with a period of validity
	 * and an initial value.
	 * @param initialValue The starting value for the variable
	 * @param period The period of validity of the value in milliseconds.
	 */
	public ThreadTimedCachedValue(T initialValue, long period) {
		this(period);
		this.setValue(initialValue);
	}

	/**
	 * Method that change the validity time of the {@link CachedValue}.
	 * @param validityTime The validity time of the {@link CachedValue} in milliseconds
	 */
	public void setValidityTime(long validityTime) {
		this.invalidateValueRunnable.setPeriod(validityTime);
	}
	
	/**
	 * Method that return the validity time of the variable.
	 * @return The validity time in milliseconds
	 */
	public long getValidityTime() {
		return this.invalidateValueRunnable.getPeriod();
	}
	
	/*
	 * (non-Javadoc)
	 * @see com.googlecode.sparda.commons.performance.CachedValue#setExpired(boolean)
	 */
	@Override
	protected void setExpired(boolean isExpired) {
		if(isExpired)
			this.stopInvalidateThread();
		else
			this.startInvalidateThread();
		super.setExpired(isExpired);
	}
		
	/**
	 * Method that start the invalidator {@link Thread}.
	 * This check that only one invalidator {@link Thread} must be active.
	 * If an old {@link Thread} is active it is interrupted and is created a new {@link Thread}.
	 *
	 */
	private void startInvalidateThread() {
		this.stopInvalidateThread();//stop old invalidate Thread
		this.lastThread = new Thread(INVALIDATOR_THREADGROUP, this.invalidateValueRunnable);
		this.lastThread.start();
	}
	
	/**
	 * Method that stop the invalidator {@link Thread}.
	 *
	 */
	private void stopInvalidateThread() {
		if(!this.lastThread.isInterrupted())
			this.lastThread.interrupt();
	}
	
	/**
	 * This class is the invalidation work.
	 * @author Fabio Grucci (<i>alias</i> <b>Phoenix</b>)
	 * @version 1.0
	 * @since 1.0
	 */
	private final class InvalidateValue implements Runnable {

		/**
		 * The period, in millisecond, to wait until invalidate the {@link ThreadTimedCachedValue}.
		 */
		private long period;
		
		/**
		 * Build a {@link InvalidateValue} with a wait time.
		 * @param period The time to wait invalidate the {@link ThreadTimedCachedValue}
		 */
		public InvalidateValue(long period) {
			this.period = period;
		}
		
		/**
		 * Return the validity time.
		 * @return period The validity period of the {@link ThreadTimedCachedValue} in milliseconds
		 */
		public long getPeriod() {
			return period;
		}

		/**
		 * Method to set the validity time of the variable in milliseconds.
		 * @param period the validity time to set in milliseconds
		 */
		public void setPeriod(long period) {
			this.period = period;
		}
		
		/* (non Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			try {
				Thread.sleep(this.period);
				ThreadTimedCachedValue.this.expireCache();
			} catch (InterruptedException e) {/* do nothing */}
		}
	}
}
